#include "stdafx.h"
#include "NCUCModel.h"
#include<boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
NCUCModel::NCUCModel()
{
}
NCUCModel::NCUCModel(const string &conf)
{
	config(conf);
}
NCUCModel::NCUCModel(const string &conf, const doubleContainer & paras)
{
	config(conf);
	parameters(paras);	
}	
NCUCModel::NCUCModel(const NCUCModel &rhs)
{
	duplicate(rhs);
}

NCUCModel::~NCUCModel()
{
}
// copy assignment
NCUCModel& NCUCModel::operator=(const NCUCModel &rhs)
{
	duplicate(rhs);
	return (*this);
}
NCUCModel* NCUCModel::clone() const
{
	return new NCUCModel(*this);
}
void NCUCModel::config(const string &conf)
{
	using namespace boost;
	tokenizer<> tok(conf);
	string tmp;
	NCULabel tag;
	tmp.clear();
	for ( tokenizer<>::iterator iter = tok.begin(); 
		iter != tok.end();
		++ iter)
	{
		tmp = *iter; // get token
		tag = label( tmp ); // transform the token to a tag
		m_seq.push_back( factory( tag ) ); // generate a NCUPTR according to the tag
	}
}
const string NCUCModel::config() const
{
	string conf;
	for (NCUCModel::NCUConstContainerIter iter = m_seq.begin();
		iter != m_seq.end();
		++ iter)
	{
		conf += (*iter)->configString();
		if ( iter != (--m_seq.end()) )
			conf += " ";
	}
	return conf;
}
/*************************************
assign parameters into each NCU.
if sucess, return true.
else return false.
*************************************/
bool NCUCModel::parameters(const doubleContainer &paras)
{
	if (number() != paras.size() ) return false;
	vector<double>::const_iterator first = paras.begin(),
		last = paras.begin();
	vector<double> tmp;
	for ( NCUCModel::NCUContainerIter iter = m_seq.begin();
        iter != m_seq.end();
		++ iter)
	{
        last += ((*iter)->number()); // advance the iterator in num steps
		tmp.assign(first, last);
		(*iter)->parameters(tmp);
		first = last; // updating iterators to the next locations
	}
	return true;
}
const NCUCModel::doubleContainer NCUCModel::parameters() const
{
	NCUCModel::doubleContainer paras, tmp;
	for (NCUCModel::NCUConstContainerIter iter = m_seq.begin();
		iter != m_seq.end();
		++ iter)
	{
		tmp = (*iter)->parameters();
		paras.insert(paras.end(), tmp.begin(), tmp.end());
	}
	return paras;
}
vector<double> NCUCModel::simulation(const doubleContainer &input)
{
	reset(); // reset the status of the NCUCModel
	NCUCModel::doubleContainer hydrograph;
	for (NCUCModel::doubleConstIter piter = input.begin();
		piter != input.end();
		++ piter)
	{
        hydrograph.push_back(
            step( *piter ).first
            );
    }
	reset();
	return hydrograph;
}
void NCUCModel::reset()
{
	for (NCUCModel::NCUContainerIter iter = m_seq.begin();
		iter != m_seq.end();
		++ iter)	
	{
		(*iter)->reset();
	}
}
const unsigned int NCUCModel::number() const
{
	unsigned int num = 0;
	for (NCUCModel::NCUConstContainerIter iter = m_seq.begin();
		iter != m_seq.end();
		++ iter)
	{
		num += (*iter)->number();
	}
	return num;
}
vector<const GenomeType*> NCUCModel::genome() const
{
	vector<const GenomeType*> genomeList, tmp;
	for ( NCUCModel::NCUConstContainerIter iter = m_seq.begin();
        iter != m_seq.end();
		++ iter)
	{
		tmp = (*iter)->genome();
		genomeList.insert( genomeList.end(),
			tmp.begin(), tmp.end()
			);
	}
	return genomeList;
}
// assign the boundaries of sill variables
void NCUCModel::sillBound(const double lower, const double upper,
							const unsigned int length)
{
    for ( NCUCModel::NCUContainerIter iter = m_seq.begin();
        iter != m_seq.end();
        ++ iter)
	{
			(*iter)->sillLower(lower);
			(*iter)->sillUpper(upper);
			(*iter)->sillLength(length);
	}

}
// assign the boundaries of vent coefficients
void NCUCModel::ventBound(const unsigned int length)
{
    for ( NCUCModel::NCUContainerIter iter = m_seq.begin();
        iter != m_seq.end();
        ++ iter)
	{
			(*iter)->ventLength(length);
	}
}
// a factory generates a instance of a NCU with a specification specified by users
NCUCModel::NCUPTR NCUCModel::factory(const NCULabel &tag)
{
	switch(tag.identity())
	{
		case TypeA:
			return NCUPTR( new TypeANCU(tag.numCV()) );
			break;
		case TypeB:
			return NCUPTR( new TypeBNCU(tag.numCV()) );
			break;
		case TypeC:
			return NCUPTR( new TypeCNCU(tag.numCV()) );
			break;
		case TypeD:
			return NCUPTR( new TypeDNCU(tag.numCV()) );
			break;
			// TO DO
			// An error message should be throw when a wrong label is given.
	}
}
// converting a string into a NCULabel
NCULabel NCUCModel::label(const string &tag)
{
	using namespace boost::algorithm;
	NCULabel id;
	if ( check_id(tag[0]) )
		id.identity(tag[0]);
	else
		errMsg = msg[4];

	string::const_iterator iter = tag.begin();
	++ iter;
	string tmp( iter, tag.end() );
	if ( all( tmp, is_digit() ))
	{
        id.numCV(
            atoi(tmp.c_str())
            );
	}
	return id;
}
bool NCUCModel::check_id(const char &id)
{
	switch(id)
	{
        case TypeA:
        case TypeB:
		case TypeC:
		case TypeD:
			return true;
		default:
			return false;
	}
}
	// duplicate a NCUCModel in the right hand side
void NCUCModel::duplicate(const NCUCModel &rhs)
{
	m_seq.clear();
	NCUCModel::NCUConstContainerIter iter = rhs.m_seq.begin();
	for(; iter != rhs.m_seq.end(); ++ iter)
	{
		m_seq.push_back(
			NCUPTR( (*iter)->clone() )
			);
	}
}
const outputPair NCUCModel::step(const double &input) const
{
    double runoff = 0, leak;
	leak = input;
	outputPair tmp;
	for (NCUCModel::NCUConstContainerIter iter = m_seq.begin();
        iter != m_seq.end();
        ++ iter)
	{
		tmp = (*iter)->simulation(leak);
		runoff += tmp.first;
		leak = tmp.second;
	}
	return outputPair(runoff, leak);
}

// assign the area(in square km) to the EDCFactor
void NCUCModel::area(const double& a)
{
	m_factor.area(a);
}

	// assign the simulation time interval (in minutes)
void NCUCModel::interval(const unsigned int& i)
{
	m_factor.interval(i);
}

vector<double> NCUCModel::runoff(const doubleContainer &input)
{
	vector<double> a = simulation(input);
	m_factor.factor();
	vector<double> b;
	transform(a.begin(), a.end(), back_inserter(b), m_factor);
	return b;
}